package cn.iocoder.yudao.framework.excel.core.util;

import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.converters.longconverter.LongStringConverter;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Excel 工具类
 *
 * @author 芋道源码
 */
@Slf4j
public class ExcelUtils {

    /**
     * 将列表以 Excel 响应给前端
     *
     * @param response  响应
     * @param filename  文件名
     * @param sheetName Excel sheet 名
     * @param head      Excel head 头
     * @param data      数据列表哦
     * @param <T>       泛型，保证 head 和 data 类型的一致性
     * @throws IOException 写入失败的情况
     */
    public static <T> void write(HttpServletResponse response, String filename, String sheetName,
                                 Class<T> head, List<T> data) throws IOException {
        // 输出 Excel
        EasyExcel.write(response.getOutputStream(), head)
                .autoCloseStream(false) // 不要自动关闭，交给 Servlet 自己处理
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度，自动适配。最大 255 宽度
                .registerWriteHandler(new SelectSheetWriteHandler(head)) // 基于固定 sheet 实现下拉框
                .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度
                .sheet(sheetName).doWrite(data);
        // 设置 header 和 contentType。写在最后的原因是，避免报错时，响应 contentType 已经被修改了
        response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.name()));
        response.setContentType("application/vnd.ms-excel;charset=UTF-8");
    }

    public static <T> void write(HttpServletResponse response, String filename, String sheetName,
                                 Class<T> head, List<T> data,List<List<String>> headers) throws IOException {


        EasyExcel.write(response.getOutputStream(),head)
                .autoCloseStream(false) // 不要自动关闭，交给 Servlet 自己处理
                .registerWriteHandler(ExcelStyleTool.getColumnWidthStrategy(20,100)) // 基于 column 长度，自动适配。最大 255 宽度  ExcelStyleTool.getColumnWidthStrategy(20,100)
                .registerWriteHandler(ExcelStyleTool.getStyleStrategy())
                //.registerWriteHandler(new SelectSheetWriteHandler(head)) // 基于固定 sheet 实现下拉框
                .registerWriteHandler(new TotalRowHandler(data.size()))
                .registerConverter(new LongStringConverter())
                .head(headers) // 避免 Long 类型丢失精度
                .sheet(sheetName).doWrite(data);
        // 设置 header 和 contentType。写在最后的原因是，避免报错时，响应 contentType 已经被修改了
        response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.name()));
        response.setContentType("application/vnd.ms-excel;charset=UTF-8");
    }

    public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException {
        return EasyExcel.read(file.getInputStream(), head, null)
                .autoCloseStream(false)  // 不要自动关闭，交给 Servlet 自己处理
                .doReadAllSync();
    }

    /**
     *
     * @param ifSum 是否写入“合计”汉字
     * @param sumIndex 写入“合计”的列下标
     * @param indexSet 需要计算合计的列的下标集合，不能重复
     * @param records 数据集
     * @return
     */
    public static <T> T sumCell(Boolean ifSum, Integer sumIndex, HashSet<Integer> indexSet, List<T> records, Class<T> clazz) throws InstantiationException, IllegalAccessException {
        // 获取类的所有属性
        Field[] declaredFields = clazz.getDeclaredFields();
        T t = clazz.newInstance();
        for (Field field: declaredFields) {
            field.setAccessible(true);
            ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
            if (annotation != null) {
                int index = annotation.index();
                if (ifSum) {
                    if (index == sumIndex) {
                        field.set(t,"合计");
                    }
                }
                if (indexSet.contains(index)){
                    String type = field.getType().getName();
                    // 进行合并的列一般为数值型，为更具通用性，判断是否是String类型,统一转成BigDecimal
                    BigDecimal total = BigDecimal.ZERO;
                    for (T temp : records){
                        BigDecimal count = new BigDecimal(String.valueOf(field.get(temp)));
                        total = total.add(count);
                    }
                    if (type.contains("String")){
                        // 转成String类型
                        field.set(t,total.toString());
                    }else if (type.contains("Integer")){
                        field.set(t,total.intValue());
                    }else if (type.contains("Double")){
                        field.set(t,total.doubleValue());
                    }else if(type.contains("BigDecimal")){
                        field.set(t,total);
                    }
                }
            }
        }
        return t;
    }






    /**
     * 写入合计行,直接写入导出数据里，数据为空不会写入
     * 先找出所有需要进行合计的列，遍历这一列的数据，进行累加操作，最后将计算出的合计写入到最后一行
     *
     * @param headList     表头
     * @param dataList     数据
     * @param totalHeadSet 需要合计的列表头名称
     */
    private static void addTotalRow(List<List<String>> headList, List<List<String>> dataList, HashSet<String> totalHeadSet) {
        if (!CollectionUtils.isEmpty(totalHeadSet)) {
            // 需要合计的列，表头和列下标
            List<TotalHeadData> headIndexData = new ArrayList<>();
            for (int i = 0; i < headList.size(); i++) {
                // i是表头的列下标
                List<String> stringList = headList.get(i);
                String head = stringList.get(0);
                // 需要合计的表头
                if (totalHeadSet.contains(head)) {
                    TotalHeadData headData = new TotalHeadData();
                    headData.setHead(head);
                    headData.setIndex(i);
                    headIndexData.add(headData);
                }
            }
            // 下标-数据对象
            Map<Integer, TotalHeadData> indexTotalHeadData = headIndexData
                    .parallelStream()
                    .collect(Collectors.toMap(TotalHeadData::getIndex, v -> v));
            // 遍历行
            for (List<String> rowList : dataList) {
                // 遍历列
                for (int i = 0; i < rowList.size(); i++) {
                    if (indexTotalHeadData.containsKey(i)) {
                        TotalHeadData totalHeadData = indexTotalHeadData.get(i);
                        // 累计数据
                        String valueTotal = totalHeadData.getValue();
                        BigDecimal total = BigDecimal.ZERO;
                        if (StringUtils.isNotEmpty(valueTotal)) {
                            try {
                                total = new BigDecimal(valueTotal);
                            } catch (Exception e) {
                                log.error("String转换BigDecimal失败，累计值：{}，错误信息：{}", valueTotal, e.getMessage());
                            }
                        }
                        // 本次数据
                        String valueNow = rowList.get(i);
                        BigDecimal decimalNow = BigDecimal.ZERO;
                        if (StringUtils.isNotEmpty(valueNow)) {
                            try {
                                decimalNow = new BigDecimal(valueNow);
                            } catch (Exception e) {
                                log.error("String转换BigDecimal失败，单元格内容：{}，错误信息：{}", valueNow, e.getMessage());
                            }
                        }
                        total = total.add(decimalNow);
                        totalHeadData.setValue(String.valueOf(total));
                    }
                }
            }
            // 写入合计行
            List<String> totalRow = new ArrayList<>();
            // 写入行数据；如果业务明确，可以将此处改为fori，在第一列写入“合计”二字，便于查看
            for (List<String> ignored : headList) {
                String value = "";
                totalRow.add(value);
            }
            // 修改行数据
            for (int i = 0; i < totalRow.size(); i++) {
                if (indexTotalHeadData.containsKey(i)) {
                    TotalHeadData totalHeadData = indexTotalHeadData.get(i);
                    totalRow.set(i, totalHeadData.getValue());
                }
            }
            dataList.add(totalRow);
        }
    }



    @Data
    static class TotalHeadData {
        /**
         * 表头
         */
        private String head;

        /**
         * 下标
         */
        private Integer index;

        /**
         * 数据
         */
        private String value;
    }



}
